﻿$_migratorfoo = @{}

# gets the migratorfoo.exe path
function _migratorfoo-exe-path { $_migratorfoo.migratorFooPath }

# look through all the projects and get the first migratorfoo.xml configuration file
# returns the item object
function _config-item {
    Write-Verbose "checking if we already found our config..."
    
    if ($_migratorfoo.configItem -eq $null) {
        Write-Verbose "we haven't found our config -- looking for it now..."
        
        $_migratorfoo.configItem =
            $dte.Solution.Projects | 
            foreach { Get-ProjectItems $_.ProjectItems } | 
            where { $_.Name -eq "migratorfoo.xml" } | 
            select -first 1
            
        Write-Verbose "we should have a config now..."
    }
    else {
        Write-Verbose "in theory we have a configItem"
    }
    
    $_migratorfoo.configItem
}

# finds the first migratorfoo.xml that is part of the solution and 
# returns the absolute local path
function _config-path {
    Write-Verbose "finding migratorfoo config xml..."
    if (!$dte) { return }
    
    $configItem = _config-item
    if (!$configItem) { return }
    
    $configPath = _projectitem-localpath $configItem
        
    Write-Host ("found config: " + $configPath)
    $configPath
}

function _projectitem-localpath($projectItem) {
    $prop = $projectItem.Properties |
        where { $_.Name -eq "LocalPath" } | 
        select -First 1
    
    $foundProp = $prop -ne $null
    $prop.Value   
}

# gets the project that contains the migratorfoo config
function _migratorfoo-project {
    $configItem = _config-item
    if (!$configItem) { return }
    
    $configItem.ContainingProject
}

# compile the project that contains the migration config
function _compile-migration-assembly {
    Write-Host "compiling migration assembly"
    $proj = _migratorfoo-project
    msbuild $proj.FullName /verbosity:q
}

# updates the project to include new migrations and removes any deleted migratioins.
function _add-new-migrations-to-proj {
    Write-Verbose "adding new migrations to project"
    $proj = _migratorfoo-project
    $items = Get-ProjectItems($proj.ProjectItems) | where { $_.Name -like "*.sql" }
    $itemNames = $items | select Name
    $itemNames | foreach { Write-Verbose $_ }
    
    $migrationRoot = Split-Path $(_config-path)
    #Write-Verbose $migrationRoot
    
    #add new migrations
    $migrations = gci $migrationRoot -Recurse -Include *.sql
    if ($migrations) {
        $migrations | foreach { 
            $migrationName = (Split-Path $_ -Leaf)
            Write-Verbose "check migration: $migrationName"
            if (($itemNames -contains $migrationName) -eq $false) {
                Write-Host ("adding migration: " + (Split-Path $_ -Leaf))
                $__swallow = $proj.ProjectItems.AddFromFile($_)
            }
        }
    }
    
    #remove deleted migrations
    Write-Verbose "removing any orphanded migrations"
    if ($items) {
        $items | foreach {
            $itemPath = _projectitem-localpath $_
            
            if ((Test-Path $itemPath) -eq $false) {
                Write-Host ("migration is orphaned, deleting: " + (Split-Path $itemPath -Leaf))
                $_.Remove()
            }
        }
    }
    
    $proj.Save()
}

# gets project items as a flat list (recursive)
function Get-ProjectItems($projItems) {
    $found = @()
    foreach ($item in $projItems) {
        # if this has child projectitems
        if ($item.SubProject) {
            $found + $(Get-ProjectItems($item.SubProject))
        }
        elseif ($item.ProjectItems.count -gt 0) { 
            # we have a collection.  Recurse in and add to our found items
            $found + $(Get-ProjectItems($item.ProjectItems)) 
        }
        else { 
            # single item, add it
            $found + $item 
        }
    }
    
    return $found
} Export-ModuleMember Get-ProjectItems


<#
    .SYNOPSIS

    must be called by the installer to set module parameters
#>
function Initialize-MigratorFoo {
    param(
        [Parameter(Position=0,Mandatory=1)]
        $toolsPath)
    
    $_migratorfoo.toolsPath = $toolsPath
    
    # a location of the migratorfoo exe that is static
    # useful for build scripts that don't want to keep up with the version numbers
    $migratorFooSource = Join-Path $toolsPath "migratorfoo\*"
    
    # put migrator foo in root "packages" dir
    $staticMigratorFoo = Join-Path $toolsPath "..\..\_migratorfoo"
    
    Write-Host "copying migratorfoo to static path:$migratorFooSource -> $staticMigratorFoo"
    if (Test-Path $staticMigratorFoo) { Remove-Item $staticMigratorFoo -Recurse -Force }
    New-Item $staticMigratorFoo -type directory
    Copy-Item $migratorFooSource -Destination $staticMigratorFoo -Recurse -Force
    
    $_migratorfoo.migratorFooPath = (Join-Path $staticMigratorFoo "migratorfoo.exe")
    
} Export-ModuleMember initialize-migratorfoo

<#
    .SYNOPSIS
    
    Generates migrations, use -force to remove any pending migrations.
    Will error out if there are pending migrations and -force is not used.
#>
function Update-Migrations {
    [CmdletBinding()]
    param(
        # if present, will remove any pending migrations before generating the
        # new migration set.  The same as using Remove-PendingMigrations then
        # Update-Migrations.
        [switch] $Force)
    
    $configPath = _config-path
    _compile-migration-assembly

    $cmdOpts = ""
    if ($Force) { $cmdOpts += "/t:regen /force" }
    else {$cmdOpts += "/t:generate" }
    
    $cmdOpts += " /c:$configPath"
    $cmd = "$(_migratorfoo-exe-path) $cmdOpts"
    
    Write-Verbose "running with command opts: $cmd"
    Invoke-Expression $cmd

    _add-new-migrations-to-proj
    
} Export-ModuleMember Update-Migrations

<#
    .SYNOPSIS
    
    force the removal of any pending migration files
#>
function Remove-PendingMigrations {
    [CmdletBinding()]
    param()

    $configPath = _config-path
    
    Invoke-Expression "$(_migratorfoo-exe-path) /t:remove-pending /c:$configPath"
    
    _add-new-migrations-to-proj
} Export-ModuleMember Remove-PendingMigrations

<#
    .SYNOPSIS
    
    Nukes a database: drops the database then recreates it from all existing migrations.
#>
function Invoke-DbNuke {
    [CmdletBinding()]
    param(
        # if present don't prompt for confirmation to the nuke command
        [switch] $Force,
        
        # explicitly define the db config name (if empty, uses 'default')
        [string] $DbConfigName,
        
        # explicitly supply a master connection string, otherwise
        # it is infered from the db config connection
        [string] $MasterConnectionString)
    
    if (!$Force) {
        $confirmNuke = Read-Host "This will drop the currently configured database!!! continue? (Y/N)"  
        if ($confirmNuke.ToUpperInvariant() -ne "Y") {
            Write-Host "nuke aborted..."
            return
        }
    }
    
    $configPath = _config-path
    $cmd = "$(_migratorfoo-exe-path) /t:nuke /c:$configPath /force"
    if ($DbConfigName) { $cmd += " /n:$DbConfigName" }
    if ($MasterConnectionString) { $cmd += " /m:$MasterConnectionString" }
    
    Invoke-Expression $cmd
} Export-ModuleMember Invoke-DbNuke

<#
    .SYNOPSIS
    
    Undo the last applied migration on the database.
#>
function Undo-LastMigration {
    [CmdletBinding()]
    param()
    
    $configPath = _config-path
    $cmd = "$(_migratorfoo-exe-path) /t:undo /c:$configPath"
    
    Invoke-Expression $cmd
} Export-ModuleMember Undo-LastMigration

<#
    .SYNOPSIS
    
    Syncs pending migrations to the database
#>
function Sync-Migrations {
    [CmdletBinding()]
    param(
        # if present, will apply migrations in sub folders as well.
        # otherwise, only will apply migratioon
        [switch] $All,
        
        # explicity specify the version to sync to.  If not included,
        # will sync to the most recent unapplied version.  
        # NOTE: ignored if -All is specified.
        [string] $ToVersion,
        
        # explicitly define the db config name (if empty, uses 'default')
        [string] $DbConfigName)
    
    $configPath = _config-path
    $cmd = "$(_migratorfoo-exe-path) /c:$configPath"
    
    if ($DbConfigName) { $cmd += " /n:$DbConfigName" }
    if ($Verbose) { $cmd += " /verbose" }
    
    if ($All) { 
        $cmd += " /t:apply-all" 
    }
    else { 
        $cmd += " /t:apply" 
        if ($ToVersion) { $cmd += "=$ToVersion" }
    }
    
    Write-Verbose "running command: $cmd"
    Invoke-Expression $cmd
} Export-ModuleMember Sync-Migrations

<#
    .SYNOPSIS
    
    Gets all the db config names in the configured assembly.
#>
function Get-DbConfigNames {
    [CmdletBinding()]
    param()
    
    $configPath = _config-path
    
    Invoke-Expression "$(_migratorfoo-exe-path) /t:list-dbconfigs /c:$configPath"
} Export-ModuleMember Get-DbConfigNames